package org.openkanban.server.service.impl;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.dozer.DozerBeanMapper;
import org.openkanban.server.dao.ActivityDao;
import org.openkanban.server.domain.Activity;
import org.openkanban.server.domain.Board;
import org.openkanban.server.domain.Kanban;
import org.openkanban.server.domain.User;
import org.openkanban.server.service.ActivityService;
import org.openkanban.server.service.BoardService;
import org.openkanban.server.service.HistoryService;
import org.openkanban.server.service.KanbanService;
import org.openkanban.shared.dto.KanbanDto;
import org.openkanban.shared.util.CollectionStringTransformer;
import org.openkanban.shared.util.HistoryAction;
import org.openkanban.shared.util.StatusCodes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * standard service implementation of {@code ActivityService}
 */
@Service
public class ActivityServiceDefaultImpl implements ActivityService {

    @Autowired
    private ActivityDao activityDao;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private KanbanService kanbanService;

    @Autowired
    private BoardService boardService;

    @Autowired
    private DozerBeanMapper mapper;

    @Override
    public Activity getActivity(Long activityId) {
	return activityDao.getActivity(activityId);
    }

    @Override
    public List<Activity> getAll() {
	return activityDao.getAll();
    }

    @Override
    public List<Activity> getAllByBoardId(Long boardId) {
	return activityDao.getAllByBoardId(boardId);
    }

    @Override
    public Activity createActivity(Activity activity) {
	return activityDao.createActivity(activity);
    }

    @Override
    @Transactional
    public StatusCodes removeActivity(Long activityId) {
	Board board = boardService.getBoardByActivityIdLong(activityId);
	Activity activityToRemove = activityDao.getActivity(activityId);
	board.getActivities().remove(activityToRemove);
	return activityDao.removeActivity(activityToRemove);
    }

    @Override
    public Activity updateActivity(Activity activity) {
	return activityDao.updateActivity(activity);
    }

    @Override
    @Transactional
    public KanbanDto addKanbanToActivity(Long userId, Long parentActivityId, KanbanDto kanbanDto) {
	CollectionStringTransformer cst = new CollectionStringTransformer(new HashSet<Long>(), kanbanDto.getKanbanUserString());
	Collection<Long> kanbanUser = cst.toCollection();
	if (kanbanUser != null && kanbanUser.size() > 0) {
	    kanbanDto.setKanbanUserIds((Set<Long>) kanbanUser);
	}

	Kanban newKanban = kanbanService.createKanban(kanbanDto.getTitle(), kanbanDto.getDescription(), kanbanDto.getEffort(),
		kanbanDto.getKanbanUserIds());
	Kanban kanban = activityDao.addKanbanToActivity(parentActivityId, newKanban);

	if (kanban != null) {
	    historyService.writeHistoryEntry(userId, parentActivityId, HistoryAction.CREATE_KANBAN, newKanban.getHistoryText());
	} else {
	    return new KanbanDto(StatusCodes.KANBAN_ERROR);
	}

	KanbanDto newKanbanDto = mapper.map(kanban, KanbanDto.class);

	String userIdsString = createUserIdString(kanban);
	newKanbanDto.setKanbanUserString(userIdsString);
	return newKanbanDto;
    }

    private String createUserIdString(Kanban kanban) {
	String userIdsString = null;
	if (kanban.getKanbanUser() != null && kanban.getKanbanUser().size() > 0) {
	    Set<Long> tmpUserId = new HashSet<Long>();
	    for (User user : kanban.getKanbanUser()) {
		tmpUserId.add(user.getUserId());
	    }
	    CollectionStringTransformer tmpCst = new CollectionStringTransformer(tmpUserId);
	    userIdsString = tmpCst.toString();
	}
	return userIdsString;
    }

    @Override
    @Transactional
    public KanbanDto moveKanbanToActivity(Long userId, Long newParentActivityId, Long kanbanId) {
	Activity sourceActivity = activityDao.getActivityByKanbanId(kanbanId);

	if (!isKanbanMoveAllowed(sourceActivity.getActivityId(), newParentActivityId)) {
	    return new KanbanDto(StatusCodes.INVALID_KANBAN_TARGET);
	}

	Kanban kanban = activityDao.moveKanbanToActivity(newParentActivityId, kanbanId);
	kanban.setPullable(false);

	if (kanban != null) {
	    if (isMovedBackward(newParentActivityId, sourceActivity.getActivityId())) {
		historyService.writeHistoryEntry(userId, newParentActivityId, HistoryAction.PULL_KANBAN_BACK, kanban.getHistoryText());
	    } else if (isDifferentActivty(newParentActivityId, sourceActivity)) {
		historyService.writeHistoryEntry(userId, newParentActivityId, HistoryAction.PULL_KANBAN, kanban.getHistoryText());
	    }
	}

	return mapper.map(kanban, KanbanDto.class);
    }

    @Override
    public Activity getActivityByKanbanId(Long kanbanId) {
	return activityDao.getActivityByKanbanId(kanbanId);
    }

    private boolean isKanbanMoveAllowed(Long sourceActivityId, Long targetActivityId) {
	if (activityIdsBelongToTheSameBoard(sourceActivityId, targetActivityId)) {
	    if (isMovedBackward(targetActivityId, sourceActivityId)) {
		return true;
	    }

	    Long boardId = boardService.getBoardByActivityIdLong(sourceActivityId).getBoardId();
	    List<Activity> activityList = activityDao.getActivityRange(boardId, sourceActivityId, targetActivityId);
	    if (activityList.size() == 2) {
		return true;
	    }
	}
	return false;
    }

    private boolean activityIdsBelongToTheSameBoard(Long... activityIds) {
	if (activityIds != null && activityIds.length != 0) {
	    if (activityIds.length == 1) {
		return true;
	    }
	    Long tempBoardId = null;
	    for (Long activityId : activityIds) {
		if (tempBoardId == null) {
		    tempBoardId = boardService.getBoardByActivityIdLong(activityId).getBoardId();
		} else {
		    Long currBoardId = boardService.getBoardByActivityIdLong(activityId).getBoardId();
		    if (tempBoardId != currBoardId) {
			return false;
		    }
		    tempBoardId = currBoardId;
		}
	    }
	}
	return true;
    }

    private boolean isDifferentActivty(Long parentActivityId, Activity sourceActivity) {
	return parentActivityId != sourceActivity.getActivityId();
    }

    private boolean isMovedBackward(Long targetActivityId, Long sourceActivityId) {
	return sourceActivityId > targetActivityId;
    }

}
